#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2017/8/7 14:55
# @Author  : Fairy Huang
# @File    : cookies.py
# @Project: LabCrawler

import base64
import os
import requests
import time
import json
import logging
import urllib.parse
import re
import rsa
import base64
import binascii
import random
from urllib.parse import urlparse
import datetime
import uuid
import settings
from tools import yundama
from utils import errors, common
import requests
import pymysql


"""
    输入你的微博账号和密码，可去淘宝买，一元5个。
    建议买几十个，实际生产建议100+，微博反爬得厉害，太频繁了会出现302转移。
"""

class MyCookies:
    myWeiBo = []

    __client_js_ver__ = 'ssologin.js(v1.4.15)'
    headers = {
        "Host": "login.sina.com.cn",
        "Referer": "http://weibo.com/login.php",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0"
    }

    name = 'cookies'

    login_data = {
        "door": "",
        "encoding": "UTF-8",
        "entry": "weibo",
        "from": "",
        "gateway": "1",
        "nonce": "",
        # "pagerefer": "http://login.sina.com.cn/sso/logout.php?entry=miniblog&r=http://weibo.com/logout.php?backurl=/",
        "pcid": "",
        "prelt": "196",  # 似乎随机
        "pwencode": "rsa2",
        "qrcode_flag": "false",
        "returntype": "META",
        "rsakv": "",
        "savestate": "7",
        "servertime": "",
        "service": "miniblog",
        "sp": "",
        "sr": "1366*768",
        "su": "",
        "url": "http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack",
        "useticket": "1",
        "vsnf": "1"
    }

    # 验证码类型，# 例：1005表示5位字母数字，不同类型收费不同。请准确填写，否则影响识别率。在此查询所有类型 http://www.yundama.com/price.html
    codetype = 1005

    def __init__(self):
        self.log = common.log_helper(self.name)

    def prelogin_url(self,account):
        url = 'http://login.sina.com.cn/sso/prelogin.php?'
        data = {
            "entry": "weibo",
            "callback": "sinaSSOController.preloginCallBack",
            "rsakt": "mod",
            "checkpin": 1,
            "su": self.__encode_username(account),
            "_": int(time.time() * 1000),
            "client": self.__client_js_ver__
        }
        data = urllib.parse.urlencode(data)
        url += data
        return url

    @property
    def login_url(self):
        url = 'http://login.sina.com.cn/sso/login.php?client={0}'.format(self.__client_js_ver__)
        return url

    def get_captcha_url(self, pcid):
        url = 'http://login.sina.com.cn/cgi/pin.php?'
        data = {
            "r": int(random.random() * 1e8),
            "s": 0,
            "p": pcid
        }
        data = urllib.parse.urlencode(data)
        url += data
        return url

    @staticmethod
    def __encode_username(username):
        return base64.b64encode(urllib.parse.quote(username).encode(encoding='utf8')).decode('utf8')

    @staticmethod
    def __encode_password(password, info):
        key = rsa.PublicKey(int(info['pubkey'], 16), 65537)
        msg = ''.join([
            str(info['servertime']),
            '\t',
            str(info['nonce']),
            '\n',
            str(password)
        ]).encode('utf8')  # To byte
        return binascii.b2a_hex(rsa.encrypt(msg, key)).decode('utf8')  # To string

    def getCookie(self, account, password):
        session = requests.Session()
        resp = session.get(url=self.prelogin_url(account), headers=self.headers)
        response_text = resp.content.decode("utf8")
        q = re.compile('preloginCallBack\((.+)\)')
        match = q.search(response_text)

        if not match:
            self.log.logger.error("No JSON Data Found in Weibo preloginCallBack!")
            raise errors.RENotMatchError

        try:
            info = json.loads(match.group(1))
            self.login_data['servertime'] = str(info['servertime'])
            self.login_data['nonce'] = info['nonce']
            self.login_data['rsakv'] = info['rsakv']
            self.login_data['pcid'] = info['pcid']
            self.login_data['su'] = self.__encode_username(account)
            self.login_data['sp'] = self.__encode_password(password, info)

        except Exception as e:
            self.log.logger.error(str(e))
            raise errors.JsonDecodeError

        if 'showpin' in info and info['showpin']:  # need to input verify code
            resp = session.get(url=self.get_captcha_url(info['pcid']), headers=self.headers)
            self.process_captcha(resp)
		
        for i in range(5):
            resp = session.post(url=self.login_url, data=self.login_data, headers=self.headers)
            if self.check_login(resp):
                cookie = session.cookies.get_dict()
                cookie = json.dumps(cookie)
                self.log.logger.debug("Get cookie for account: {0}".format(account))
                self.log.logger.debug(cookie)
                return cookie
            if 'showpin' in info and info['showpin']:
                self.log.logger.warning("Captcha not correctly recognized, retry:{0}".format(i))
                self.process_captcha("")
            else:
                break
				
        self.log.logger.debug("No cookie returned for account: {0}".format(account))
        return ""


    def process_captcha(self, response):
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "captcha")
        if settings.DEBUG:
            filename = os.path.join(path, "captcha.jpg")
        else:
            filename = settings.ROOT_DIR + "captcha.jpg"
        if response:
            with open(filename, "wb") as f:
                f.write(response.content)
        ydm = yundama.YDMHttp(**settings.YUNDAMA_ACCOUNT)
        text = ydm.identifyCaptcha(filename, self.codetype)
        self.login_data['door'] = text


    def check_login(self, response):
        # 验证服务器的返回数据判断是否成功
        p = re.compile('replace\(["\'](.+)["\']\)')
        url = p.search(response.text).group(1)

        # parse url to check whether login successfully
        query = urllib.parse.parse_qs(url, encoding='gbk')
        if int(query['retcode'][0]) == 0:  # successful
            self.log.logger.info("Successfully login to Weibo!")
            return True

        else:  # fail
            self.log.logger.error("Login to Weibo failed!")
            self.log.logger.error("query info:".format(query))
            # self.log.logger.error("ErrCode:{0}, ErrMsg:{2}".format(query['retcode'][0], query['reason'][0]))
            # raise errors.SiteLoginError
            return False


    def get_account(self, spiderName):
        db = pymysql.Connect(**settings.DB_CONN_DICT)

        # 获取游标
        cur = db.cursor()
        # 执行sql语句，进行查询
        sql = "SELECT sa.username, sa.password FROM site_account AS sa, site AS s \
        WHERE sa.site_id = s.id AND s.spider_name='%s'" % spiderName
        cur.execute(sql)
        result = cur.fetchall()
        if not result:
            self.log.logger.error("No username and password found for Spider:{0}".format(spiderName))
            raise errors.DBNotFoundError
        for item in result:
            self.myWeiBo.append((item[0],item[1]))

        cur.close()
        db.close()


    def initCookie(self, rconn, spiderName):
        """ 获取所有账号的Cookies，存入Redis。如果Redis已有该账号的Cookie，则不再获取。 """
        self.get_account(spiderName)
        for weibo in self.myWeiBo:
            if rconn.get("%s:Cookies:%s--%s" % (spiderName, weibo[0], weibo[1])) is None:  # 'SinaSpider:Cookies:账号--密码'，为None即不存在。
                cookie = self.getCookie(weibo[0], weibo[1])
                if len(cookie) > 0:
                    rconn.set("%s:Cookies:%s--%s" % (spiderName, weibo[0], weibo[1]), cookie)
        cookieNum = "".join([common.bytes_to_str(item) for item in rconn.keys()]).count("%s:Cookies" % spiderName)
        self.log.logger.warning("The num of the cookies is %s" % cookieNum)
        if cookieNum == 0:
            self.log.logger.warning('The num of the cookies is 0')
            self.log.logger.warning('Stopping...')
            os.system("pause")


    def updateCookie(self, accountText, rconn, spiderName):
        """ 更新一个账号的Cookie """
        account = accountText.split("--")[0]
        password = accountText.split("--")[1]
        cookie = self.getCookie(account, password)
        if len(cookie) > 0:
            self.log.logger.warning("The cookie of %s has been updated successfully!" % account)
            rconn.set("%s:Cookies:%s" % (spiderName, accountText), cookie)
        else:
            self.log.logger.warning("The cookie of %s updated failed! Remove it!" % accountText)
            self.removeCookie(accountText, rconn, spiderName)


    def removeCookie(self, accountText, rconn, spiderName):
        """ 删除某个账号的Cookie """
        rconn.delete("%s:Cookies:%s" % (spiderName, accountText))
        cookieNum = "".join([common.bytes_to_str(item) for item in rconn.keys()]).count("%s:Cookies" % spiderName)
        self.log.logger.warning("The num of the cookies left is %s" % cookieNum)
        if cookieNum == 0:
            self.log.logger.warning("Stopping...")
            os.system("pause")